/**
 * 方法load
 *
 *  load(opt)
 *  load(fromNum, toNumber, opt)
 *  load(fromNum, taskid, opt)        新增，fromNum === 100 时
 *  load(fromNum, taskid, name, opt)  新增，fromNum === 100 时
 *  load(fromNum, toNumber, taskid, opt)      新增
 *  load(fromNum, toNumber, taskid, name, opt) 新增
 *  load(fromNum, toNumber,name)      新增，fromNum === 0 时
 *  load(name)      新增，fromNum === 0 时
 *
 * 方法closeAll
 *  closeAll()
 */
("use strict");
(function (root, factory) {
  if (typeof exports === "object") {
    module.exports = factory();
  } else if (typeof define === "function" && define.amd) {
    define([], factory);
  } else if (window.layui && window.layui.define) {
    layui.define(function (exports) {
      exports("log", factory());
    });
  } else {
    root.log = factory();
  }
})(this, function () {
  const DEFAULT_CONFIG = {
    CONTAINER: "#main",
    SPREAD_ICON: "proccess-icon-down",
    SHRINK_ICON: "proccess-icon-up",
    SHOW: "layui-show",
    PROCCESS: "proccess-out",
    PROCCESS_MAIN: "proccess-out-main",
    PROCCESS_CLOSE: "proccess-out-close",
    PROCCESS_AREA: "proccess-area",
    PROCCESS_ICON: "proccess-out-icon",
    PROCCESS_ICON_DIV: "proccess-out-icon-div",
    PROCCESS_ICON_01: "proccess-out-icon-nomal",
    PROCCESS_ICON_02: "proccess-out-icon-music",
    PROCCESS_MSG: "proccess-out-msg",
    PROCCESS_TOOL: "proccess-out-tool",
    PROCCESS_TOOL_POS: "proccess-out-tool-pos",
    PROCCESS_TOOL_BODY: "proccess-out-tool-body",
    PROCCESS_TASK: "proccess-area-task",
    PROCCESS_TEXT: "proccess-area-text",
    PROCCESS_PERCENT: "proccess-area-percent",
    PROCCESS_OUT_BAR: "proccess-area-out-bar",
    PROCCESS_OUT_CONTENT: "proccess-area-out-content",
    PROCCESS_BAR: "proccess-area-bar",
    PROCCESS_EXTRA_TASK_OUT: "proccess-extra-task-out",
    PROCCESS_EXTRA_TASK: "proccess-extra-task",
    DEFAULT_TIMES: 15,
    DELAY: 500,
    SPREAD: false,
    LOADING_MSG: "加载中,请稍后...",
  };
  var SEQUENCE = 1;
  var config =
    window.CONFIG && window.CONFIG.log
      ? _.merge(DEFAULT_CONFIG, window.CONFIG.log)
      : DEFAULT_CONFIG;
  var _Proxy = function (destination) {
    this.destination = _.isString(destination) ? $(destination) : destination;
    _initialize.initPool.call(this);
    _initialize.initBody.call(this);
    let $parent = this.parent;
    $parent.find("." + config.PROCCESS_TOOL_BODY).on("click", function () {
      if ($(this).hasClass(config.SPREAD_ICON)) {
        $(this).removeClass(config.SPREAD_ICON).addClass(config.SHRINK_ICON);
        $parent
          .find("." + config.PROCCESS_AREA)
          .removeClass(config.SHOW)
          .addClass(config.SHOW);
      } else {
        $(this).removeClass(config.SHRINK_ICON).addClass(config.SPREAD_ICON);
        $parent.find("." + config.PROCCESS_AREA).removeClass(config.SHOW);
      }
    });
    $parent
      .find("." + config.PROCCESS_CLOSE)
      .on("click", () => this.closeAll());
    this.closeAll = function () {
      _.each(this.cache.tasks, function (v) {
        if (v.action)
          v.action.find("." + config.PROCCESS_EXTRA_TASK_OUT).remove();
      });
      _initialize.initPool.call(this);
      this.parent.find("." + config.PROCCESS_AREA).empty();
      this.parent.removeClass(config.SHOW);
    };
    this.progress = function (opt = {}) {
      if (0 === opt.from) opt.taskid = this.createTask(opt);
      if (!opt.taskid) {
        console.log("没有传入taskid!");
        return null;
      }
      if (!this.cache.tasks[opt.taskid]) {
        console.log("task不存在!taskid:" + opt.taskid);
        return null;
      }
      if (0 !== opt.from) this.updateTask(opt);
      this.updateDom(opt.taskid);
      this.anime(opt.taskid);
      return opt.taskid;
    };
    this.createTask = function (opt = {}) {
      let option = _.assign(
        {
          from: 0,
          to: 100,
          current: 0,
          startTime: new Date(),
          endTime: new Date(),
          timeOut: 2 * 60 * 1000,
          name: "第" + SEQUENCE + "个任务",
          times: config.DEFAULT_TIMES,
          id: SEQUENCE,
          status: "loading...",
          cb: null,
          error: null,
        },
        opt
      );
      this.cache.tasks[option.id] = option;
      this.cache.taskPool.push(option.id);
      this.cache.hisPool.push(option.id);
      _initialize.appendTask.call(this, SEQUENCE, opt);
      if (option.action) {
        this.cache.tasks[option.id].action = _.isString(option.action)
          ? $(option.action)
          : option.action;
        _initialize.appendExtraTask(this.cache.tasks[option.id].action);
      }
      if (this.cache.taskPool.length > 1) return SEQUENCE++;
      let flag = config.SPREAD
        ? false
        : this.parent
            .find("." + config.PROCCESS_TOOL_BODY)
            .hasClass(config.SHRINK_ICON);
      if (flag) {
        this.parent
          .find("." + config.PROCCESS_TOOL_BODY)
          .removeClass(config.SHRINK_ICON)
          .addClass(config.SPREAD_ICON);
        this.parent.find("." + config.PROCCESS_AREA).removeClass(config.SHOW);
      }
      this.polling();
      return SEQUENCE++;
    };
    this.updateTask = function (opt = {}) {
      _.assign(this.cache.tasks[opt.taskid], opt, {
        current: opt.from,
        times: opt.times || config.times,
      });
      if (_.isFunction(opt.cb)) this.opt.cb = opt.cb;
    };
    this.updateDom = function (taskid, number) {
      let ele = this.parent.find("#" + config.PROCCESS_TASK + taskid),
        current = number || this.cache.tasks[taskid].current;
      ele.find("." + config.PROCCESS_TEXT).text(this.cache.tasks[taskid].name);
      ele.find("." + config.PROCCESS_PERCENT).text(current + "%");
      ele.find("." + config.PROCCESS_BAR).css({ width: current + "%" });
      this.cache.tasks[taskid].action &&
        this.cache.tasks[taskid].action
          .find("." + config.PROCCESS_EXTRA_TASK)
          .css({ width: current + "%" });
    };
    this.anime = function (taskid) {
      if (!this.cache.tasks[taskid]) {
        console.log("task not find,taskid:" + taskid);
        return null;
      }
      let current = this.cache.tasks[taskid].current;
      if (current == 100) {
        this.cache.tasks[taskid].status = "complete! ";
        this.doAnime(taskid, 100);
        this.distoryTask(taskid);
        return;
      }
      this.cache.tasks[taskid].endTime = new Date();
      let sub = this.dateSubtraction(
        this.cache.tasks[taskid].endTime,
        this.cache.tasks[taskid].startTime
      );
      if (sub >= this.cache.tasks[taskid].timeOut) {
        this.cache.tasks[taskid].status = "time out ...";
        this.parent
          .find("#" + config.PROCCESS_TASK + taskid)
          .addClass(config.PROCCESS_TIMEOUT);
        this.doAnime(taskid, 100);
        this.distoryTask(taskid);
        return;
      }
      let [to, times] = [
        this.cache.tasks[taskid].to,
        this.cache.tasks[taskid].times,
      ];
      if (!times || current == to) return this.doAnime(taskid, to);
      let splitNum = Math.ceil((to - current) / times);
      this.cache.tasks[taskid].current = splitNum + current;
      let randomNum = Math.ceil(Math.random() * splitNum) + current;
      randomNum = randomNum >= to ? to : randomNum;
      this.cache.tasks[taskid].randomNum = randomNum;
      this.cache.tasks[taskid].times--;
      this.doAnime(taskid, randomNum);
    };
    this.doAnime = function (taskid, number) {
      this.updateDom(taskid, number);
      this.setProgress();
    };
    this.setProgress = function () {
      let _kLength = _.keys(this.cache.tasks).length;
      if (_kLength == 0) return;
      let kLength = this.cache.taskPool.length;
      if (1 == kLength) {
        let taskConfig = this.cache.tasks[_.sample(this.cache.taskPool)];
        if (taskConfig.action) {
          return this.parent.removeClass(config.SHOW);
        }
        let _mainText = `${taskConfig.name} ${
          taskConfig.randomNum ? "(" + taskConfig.randomNum + "%)" : ""
        }`;
        this.parent.find("." + config.PROCCESS_MSG).text(_mainText);
      } else if (0 == kLength) {
        delete this.cache.tasks;
        this.cache.tasks = {};
        let that = this;
        setTimeout(function () {
          that.cache.taskPool.length == 0 && that.closeAll();
        }, config.DELAY);
        return;
      } else {
        let that = this;
        let bLength = _.filter(that.cache.taskPool, function (v) {
          return !!that.cache.tasks[v].action;
        }).length;
        if (kLength == bLength) return this.parent.removeClass(config.SHOW);
        let aLength = this.cache.hisPool.length;
        this.parent
          .find("." + config.PROCCESS_MSG)
          .text(
            config.LOADING_MSG + "(" + (aLength - kLength) + "/" + aLength + ")"
          );
      }
      this.parent.find("." + config.PROCCESS_AREA).css({
        width: this.parent.find("." + config.PROCCESS_MAIN).width() + "px",
      });
      this.parent.removeClass(config.SHOW).addClass(config.SHOW);
    };
    this.distoryTask = function (taskid) {
      let index = _.indexOf(this.cache.taskPool, taskid);
      this.cache.taskPool.splice(index, 1);
      this.parent
        .find("." + config.PROCCESS_AREA)
        .find("#" + config.PROCCESS_TASK + String(taskid))
        .remove();
      if (this.cache.tasks[taskid].action) {
        let dom = this.cache.tasks[taskid].action.find(
          "." + config.PROCCESS_EXTRA_TASK_OUT
        );
        this.cache.tasks[taskid].action = null;
        setTimeout(function () {
          dom.remove();
        }, config.DELAY);
      }
      this.setProgress();
    };
    this.polling = function () {
      let that = this;
      if (that.cache.taskPool.length == 0) return;
      _.each(that.cache.taskPool, function (v) {
        that.anime.call(that, v);
      });
      setTimeout(function () {
        that.polling.call(that);
      }, 1000);
    };
    this.dateSubtraction = function (a, b) {
      return Math.abs(Date.parse(a) - Date.parse(b));
    };
  };
  var _initialize = {
    initPool() {
      this.cache = {
        tasks: {},
        taskPool: [],
        hisPool: [],
      };
    },
    initBody() {
      this.parent = $(`
        <div class = "${config.PROCCESS}">
          <div class = "${config.PROCCESS_MAIN}">
            <div class = "${config.PROCCESS_CLOSE}" title = "关闭"></div>
            <div style = "position:relative;">
              <div class = "${config.PROCCESS_AREA}"></div>
            </div>
            <div class = "${config.PROCCESS_ICON} ${config.PROCCESS_ICON_01}">
              <div class = "${config.PROCCESS_ICON_DIV}"></div>
              <div class = "${config.PROCCESS_ICON_DIV}"></div>
              <div class = "${config.PROCCESS_ICON_DIV}"></div>
              <div class = "${config.PROCCESS_ICON_DIV}"></div>
            </div>
            <div class = "${config.PROCCESS_MSG}"></div>
            <div class = "${config.PROCCESS_TOOL}">
              <div class = "${config.PROCCESS_TOOL_POS}" style = "position:absolute;width:2em;height:4em" >
                <div class = "${config.PROCCESS_TOOL_BODY} ${config.SPREAD_ICON}"></div>
              </div>
            </div>
          </div>
        </div>
      `);
      this.destination.append(this.parent);
    },
    appendTask(index) {
      this.parent.find("." + config.PROCCESS_AREA).append(
        $(`
      <div id = "${config.PROCCESS_TASK}${index}" class = "${config.PROCCESS_TASK}" >
        <div class = "${config.PROCCESS_OUT_BAR}">
          <div class = "${config.PROCCESS_BAR}"></div>
        </div>
        <div class = "${config.PROCCESS_OUT_CONTENT}">
          <div class = "${config.PROCCESS_TEXT}"></div>
          <div class = "${config.PROCCESS_PERCENT}"></div>
        </div>
      </div>
      `)
      );
    },
    appendExtraTask(dom) {
      dom.append(
        $(`
        <div class = "${config.PROCCESS_EXTRA_TASK_OUT}" >
          <div class = "${config.PROCCESS_EXTRA_TASK}" ></div>
        </div>
      `)
      );
    },
  };
  var instance = function () {
    this.set = function (key, value) {
      config[key] = value;
      return this;
    };
    this.setContainer = function (container) {
      config.CONTAINER = container;
      this.closeAll();
      this.newProxy(config.CONTAINER);
      return this;
    };
    this.switchContainer = function (container) {
      return this.setContainer(container);
    };
    this.setTheme = function (boolean) {
      this.getProxy()
        .parent.find("." + config.PROCCESS_ICON)
        .removeClass(config.PROCCESS_ICON_01)
        .removeClass(config.PROCCESS_ICON_01)
        .addClass(boolean ? config.PROCCESS_ICON_02 : config.PROCCESS_ICON_01);
      return this;
    };
    this.closeAll = function () {
      this.getProxy().closeAll();
    };
    this.close = function (taskid) {
      return this.load(100, taskid);
    };

    /**
     * 控制log
     * @param {*} fromNum    开始的百分比
     * @param {*} toNumber   结束的百分比
     * @param {*} arg0       额外参数一
     * @param {*} arg1       额外参数二
     * @param {*} arg2       额外参数三
     * @returns  taskid  子任务的taskid
     *
     *  下面几种方式都正确
     *  load(opt)
     *  load(fromNum, toNumber, opt)
     *
     *  load(fromNum, taskid, opt)        新增，fromNum === 100 时
     *  load(fromNum, taskid, name, opt)  新增，fromNum === 100 时
     *  load(fromNum, toNumber, taskid, opt)      新增
     *  load(fromNum, toNumber, taskid, name, opt) 新增
     *
     *  load(fromNum, toNumber,name)      新增，fromNum === 0 时
     *  load(name)      新增，fromNum === 0 时
     *
     */
    this.load = function (fromNum, toNumber, arg0 = {}, arg1 = {}, arg2 = {}) {
      if (null == fromNum) {
        return this.getProxy().progress({ from: 0 });
      }
      if (_.isString(fromNum)) {
        return this.getProxy().progress({ name: fromNum, from: 0 });
      }
      if (!_.isFinite(fromNum)) return this.getProxy().progress(fromNum);
      let opt = _.isObject(arg0) ? arg0 : _.isObject(arg1) ? arg1 : arg2;
      if (100 == fromNum) {
        let _taskid = opt.taskid || toNumber;
        let _name = _.isObject(arg0) ? opt.name : arg0;
        opt.from = fromNum;
        opt.to = 0;
        opt.taskid = _taskid;
        if (_name) opt.name = _name;
        return this.getProxy().progress(opt);
      }
      let _taskid = opt.taskid || _.isFinite(arg0) ? arg0 : 0;
      let _name =
        opt.name || _.isString(0 == fromNum ? arg0 : arg1)
          ? 0 == fromNum
            ? arg0
            : arg1
          : null;
      opt.from = fromNum;
      opt.to = toNumber;
      if (0 !== fromNum) opt.taskid = _taskid;
      if (_name) opt.name = _name;
      return this.getProxy().progress(opt);
    };
    this.progress = function (destination, times) {
      let opt = {
        from: 0,
        to: 100,
        name: "页面加载中",
        action: destination,
        times: times || config.DEFAULT_TIMES,
      };
      return this.getProxy().progress(opt);
    };
    this.getProxy = function () {
      if (!this.proxy) this.newProxy(config.CONTAINER);
      return this.proxy;
    };
    this.newProxy = function (destination) {
      this.proxy = new _Proxy(destination);
    };
  };

  return new instance();
});
